import os

class PathData(object):
	"""PathData holds dicts, lists, str, ints, longs, aso. They can be accessed like a
	normal dict/list-access, like, for example, a["key"], or set like a["key"]=value. It's
	designed to access those values via a path-like key-string.

	For example:
		Type 1: a["key1"]["key2"]["key3"]
		Type 2: a["/key1/key2/key3"]
	"""

	TYPE_OF_DIR="dir"
	TYPE_OF_LIST="list"
	TYPE_OF_FILE="file"

	def __init__(self, data, pathSeparator=os.sep):
		"""Constructor to initialize the object.

		Parameter:
			mixed data "data" is the data which is to be hold, can be from type dict, list, PathData, str,
				int, long, float, bool, None, unicode(-string), raises a TypeError if it's not of this type, required

			string pathSeparator Defines the string the key-path-type is defined with, like "a/b/c", default os.sep, optional
		"""
		if type(data) not in (dict, list, self.__class__, str, int, long, float, bool, None, unicode): raise TypeError
		if type(pathSeparator)!=str: raise TypeError
			
		self.setData(data)
		self.setPathSeparator(pathSeparator)
	# End def

	def setPathSeparator(self, pathSeparator):
		"""Sets the path-separator-string, a key-value is splitted with.
		
		Parameter:
			string pathSeparator Defines the path-separator-string.
		"""

		if type(pathSeparator)!=str: raise TypeError
		self.__pathSeparator=pathSeparator
	# End def

	def getPathSeparator(self):
		"""Returns the path-separator-string."""

		return self.__pathSeparator
	# End def

	def setData(self, data):
		"""Sets the data, see also constructor-method."""

		self.__data=data
	# End def

	def getData(self):
		"""Returns the currently used data. see also constructor- and
		setData-methods."""

		return self.__data
	# End def

	def typeOf(self):
		"""Returns the type of the currently used data

		If type(data) is of type "dict" or "PathData" "dir" will be returned,
		type of "list" "list" will be returned, otherwise "file".

		Returns "dir", "list" or "file". See also PathData.TYPE_OF_*
		"""
		if type(self.__data) in (dict, self.__class__):
			return self.TYPE_OF_DIR
		elif type(self.__data)==list:
			return self.TYPE_OF_LIST
		else:
			return self.TYPE_OF_FILE
		# End if
	# End def

	def __getPathParts(self, key):
		"""Internal method to split the given key-value into parts, defined with
		the path-separator-string.
		
		Parameter:
			string key key-value, like type of "my-key-value" or path-string like,
				for example, "my/key/value".

		Returns:
			Returns a list with splitted key-parts. "Empty" parts will be deleted.
		"""

		parts=key.split(self.getPathSeparator())

		if len(parts)>0 and parts[0].strip()=="": del parts[0]
		if len(parts)>0 and parts[-1].strip()=="": del parts[-1]

		return parts
	# End def

	def __getitem__(self, key):
		"""Returns the value of the given key and is combined with the currently stored
		data under that key.

		Example:
			a["b"] => Returns the key-value 'b' of the data.
			a["b/c/d"] => Returns the key-value 'd' of 'c' of 'b' of the data.
			a[0] => Returns the first value, array-element 0, of the data, can be used with
				different types, like dict, list, string, aso.

		Parameter:
			string key key[-path]-string to be returned.
		"""

		if type(key) in (str, unicode) and self.getPathSeparator() in key:
			key=key.strip(self.getPathSeparator())
			parts=self.__getPathParts(key)

			e="self"
			for partNr in range(0, len(parts)):
				e+=".__getitem__(parts[%i])" % partNr
			# End for

			return eval(e)
		elif type(key)==int and type(self.__data)==dict:
			kv=self.__data.keys()
			if key<0 or key>=len(kv): raise IndexError
			return self.__class__(self.__data[kv[key]])
		elif type(self.__data)==list:
			return self.__class__(self.__data[int(key)])
		elif type(self.__data) not in (str, unicode):
			return self.__class__(self.__data[key])
		else:
			return self.__class__(d)
		# End if
	# End def

	def __setitem__(self, key, value):
		"""Sets the value of the given key.

		Example:
			a["b"]=123 => Sets value of the key 'b' to a an integer, 123.
			a["b/c/d"]="Test 456" => Sets the value of the key 'd' of 'c' of 
				'b' to a string, "Test 456".
			a[0]={"boolean": True} => Sets the value of the key '0', first element,
				to a dict-object, {"boolean": True}.

		Parameter:
			string key Defines the key[-path]-value which has to be set.
			mixed value Defines the data-value to be set.
		"""
		if type(key) in (str, unicode) and self.getPathSeparator() in key:
			parts=self.__getPathParts(key)

			e="self"
			for partNr in range(0, len(parts)-1):
				e+=".__getitem__(parts[%i])" % partNr
			# End for
			e+=".__setitem__(parts[%i], value)" % (len(parts)-1)

			eval(e)
		else:
			if type(self.__data)==list and int(key)>=len(self.__data):
				self.__data.append(value)
			else:
				self.__data[int(key) if type(self.__data)==list else key]=value
			# End if
		# End if
	# End def

	def __delitem__(self, key):
		"""Deletes the value and the key of the given key.

		Example:
			del a["b"] => Deletes the value and key under "b".
			del a["b/c/d"] => Deletes the value and key of 'd'.
			del a[0] => Deletes the first element.

		Parameter:
			string key Defines the key[-path]-value which has to be deleted.
		"""
		if type(key) in (str, unicode) and self.getPathSeparator() in key:
			parts=self.__getPathParts(key)

			e="self"
			for partNr in range(0, len(parts)-1): e+=".__getitem__(parts[%i])" % partNr
			e+=".__delitem__(parts[%i])" % (len(parts)-1)

			eval(e)
		else:
			del self.__data[int(key) if type(self.__data)==list else key]
		# End if
	# End if

	def __len__(self):
		"""Returns the length of the currently stored data."""

		return len(self.__data)
	# End def

	def __str__(self):
		return str(self.__data)
	# End def

	def has_key(self, key):
		"""Returns true if the given key-path-value is key and so available, false if not.

		Parameter:
			string key Defines the key[-path]-value which has to be checked.
		"""

		try:
			self.__getitem__(key)
			return True
		except:
			return False
		# End try/except
	# End def

	def keys(self, path=""):
		"""Returns a list of type 'list' with the keys of the currently stored data, names
		if type is of type dict or PathData, integer-range if type is of type 'list'.

		Parameter
			string path Defines the key-path-value of which the keys has to be returned.
		"""

		dataFromPath=self.__getitem__(path) if path else self.__data

		if type(dataFromPath) in (dict, self.__class__):
			return dataFromPath.keys()
		elif type(dataFromPath)==list:
			return range(0, len(dataFromPath))
		else:
			raise KeyError
		# End if
	# End def

	def values(self, path=""):
		"""Returns a list of type 'list' with the values of the currently stored data."""
		dataFromPath=self.__getitem__(path) if path else self.__data

		if type(dataFromPath) in (dict, self.__class__):
			return dataFromPath.values()
		else:
			return self.__dataFromPath
		# End if
	# End def

	def pop(self, k, d=None):
		"""Returns the value of the key-path-value and removes it from the stored data."""

		if self.has_key(k):
			v=self.__getitem__(k)
			self.__delitem__(k)
			return v.getData()
		elif d!=None:
			return d
		else:
			raise KeyError
		# End if
	# End def

# End class
